package model;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;

import com.analog.lyric.dimple.model.Discrete;

import utils.Date;
import utils.IntPair;
import utils.Pair;
import utils.TextUtils;



public class TableInformation {
	private String[][] cell_text;
	private Integer[][][] cell_entities;
	private boolean[] is_leaf_column;
	private HashMap<Integer, HashSet<Integer> > entity_to_types;
	private HashMap<Integer, HashSet<Integer> > type_to_properties;
	private HashMap<Integer, PredicateInfo> predicate_info;
	private HashMap<Integer, HashMap<Integer, HashSet<Integer> > > known_facts;
	private HashMap<Integer, HashMap<Integer, HashSet<Integer> > > reverse_facts;
	private HashMap<Integer, HashSet<Integer> > type_to_cooccurring_types;
	private HashMap<Integer, String> id_to_string_map;
	private HashMap<String, Integer> string_to_id_map;
	private HashSet<Integer> cvt_type_ids;
	private HashSet<Integer> predicates_expecting_cvts;
	private HashSet<Integer> visible_entities;
	private HashMap<IntPair, HashSet<Integer> > ent_pair_to_preds;
	
	private int num_rows;
	private int num_cols;
	private int num_strings = 0;
	
	public TableInformation(int num_rows, int num_cols) {
		this.num_rows = num_rows;
		this.num_cols = num_cols;
		cell_text = new String[num_cols][num_rows];
		cell_entities = new Integer[num_cols][num_rows][0];
		is_leaf_column = new boolean[num_cols];
		for (int c = 0; c < num_cols; c++) {
	    	is_leaf_column[c] = false;
	    	for (int r = 0; r < num_rows; r++) {
	    		cell_text[c][r] = "";
	    		cell_entities[c][r] = null;
	    	}
	    }
		entity_to_types = new HashMap<Integer, HashSet<Integer> >();
		type_to_properties = new HashMap<Integer, HashSet<Integer> >();
		predicate_info = new HashMap<Integer, PredicateInfo>();
		known_facts = new HashMap<Integer, HashMap<Integer, HashSet<Integer> > >();
		reverse_facts = new HashMap<Integer, HashMap<Integer, HashSet<Integer> > >();
		type_to_cooccurring_types = new HashMap<Integer, HashSet<Integer> >();
		id_to_string_map = new HashMap<Integer, String>();
		string_to_id_map = new HashMap<String, Integer>();
		cvt_type_ids = new HashSet<Integer>();
		predicates_expecting_cvts = new HashSet<Integer>();
		visible_entities = new HashSet<Integer>();
		ent_pair_to_preds = new HashMap<IntPair, HashSet<Integer> >();
		
		this.getOrAddIdForString("UNKTOPIC");
		this.getOrAddIdForString("UNKPRED");
		this.getOrAddIdForString("UNKTYPE");
		
		Integer float_id = this.getOrAddIdForString("/type/float");
		addTypeToOwnCooccurringSet(float_id);
		Integer int_id = this.getOrAddIdForString("/type/int");
		addTypeToOwnCooccurringSet(int_id);
		Integer datetime_id = this.getOrAddIdForString("/type/datetime");
		addTypeToOwnCooccurringSet(datetime_id);
		Integer text_id = this.getOrAddIdForString("/type/text");
		addTypeToOwnCooccurringSet(text_id);
		Integer rawstring_id = this.getOrAddIdForString("/type/rawstring");
		addTypeToOwnCooccurringSet(rawstring_id);
	}
	
	public int numRows() { return num_rows; }
	public int numVisibleCols() { return num_cols; }
	
	public String[][] getCellText() {
		return cell_text;
	}
	public void setCellText(int row, int col, String text) {
		this.cell_text[col][row] = text;
	}
	
	public Integer[][] getColumnEntities(int col) {
		return cell_entities[col];
	}
	
	public Integer[] getCellEntities(int row, int col) {
		return cell_entities[col][row];
	}
	
	public void setCellEntities(int row, int col, Integer[] entities) {
		this.cell_entities[col][row] = entities;
	}
	
	public boolean isLeafColumn(int col) {
		if (col < 0 || col >= num_cols) {
			return false;
		}
		return is_leaf_column[col];
	}
	public void setLeafColumn(int col) throws ArrayIndexOutOfBoundsException {
		if (col >= num_cols || col < 0) {
			System.err.println("Error: " + col + " is not a valid column index.");
			throw new ArrayIndexOutOfBoundsException();
		}
		this.is_leaf_column[col] = true;
	}
	
	public Integer getOrAddIdForString(String str) {
		Integer id = string_to_id_map.get(str);
		if (id == null) {
			int new_id = this.num_strings++;
			string_to_id_map.put(str, new_id);
			id_to_string_map.put(new_id, str);
			return new_id;
		}
		return id;
	}
	
	public Integer getIdForString(String str) {
		return string_to_id_map.get(str);
	}
	
	public String getStringForId(Integer id) {
		return id_to_string_map.get(id);
	}
	
	public void identifyTypeAsCvt(Integer id) {
		cvt_type_ids.add(id);
	}
	
	public void identifyPredicateAsExpectedCvt(Integer id) {
		predicates_expecting_cvts.add(id);
	}
	
	public void identifyEntityAsVisible(Integer id) {
		visible_entities.add(id);
	}
	
	public boolean isACvt(Integer id) {
		return cvt_type_ids.contains(id);
	}
	
	public boolean isACvt(String type) {
		return cvt_type_ids.contains(string_to_id_map.get(type));
	}
	
	public final Iterator<Integer> setOfCvtIdsIterator() { return cvt_type_ids.iterator(); }
	
	public void addTypesForEntity(String entity, String[] types) {
		if (entity == null || types == null)
			return;
		
		Integer entity_id = this.getIdForString(entity);
		if (entity_id == null) {
			if (!TextUtils.isMid(entity)) {
				Double num = TextUtils.parseNumericString(entity);
				if (num == null) {
					Date date = Date.parseKBDate(entity);
					if (date != null) {
						entity = date.toString();
					}
				} else {
					entity = num.toString();
				}
				entity_id = this.getOrAddIdForString(entity);
			} else
				entity_id = this.getOrAddIdForString(entity);
		}
		HashSet<Integer> type_set = this.entity_to_types.get(entity_id);
		if (type_set == null) {
			type_set = new HashSet<Integer>();
			this.entity_to_types.put(entity_id, type_set);
		}
		HashSet<Integer> ent_types = new HashSet<Integer>();
		for (int t = 0; t < types.length; t++) {
			Integer type_id = getOrAddIdForString(types[t]);
			type_set.add(type_id);
			ent_types.add(type_id);
		}
		
		for (Integer type : ent_types) {
			HashSet<Integer> cooccurring_types = type_to_cooccurring_types.get(type);
			if (cooccurring_types == null) {
				cooccurring_types = new HashSet<Integer>();
				cooccurring_types.addAll(ent_types);
				type_to_cooccurring_types.put(type, cooccurring_types);
			} else {
				cooccurring_types.addAll(ent_types);
			}
		}
	}
	
	public HashSet<Integer> getCooccurringTypes(Integer type_id) {
		return type_to_cooccurring_types.get(type_id);
	}
	
	private void addTypeToOwnCooccurringSet(Integer type_id) {
		HashSet<Integer> set = type_to_cooccurring_types.get(type_id);
		if (set == null) {
			set = new HashSet<Integer>();
			type_to_cooccurring_types.put(type_id, set);
		}
		set.add(type_id);
	}
	
//	private void addTypeForEntity(String entity, String type) {
//		if (entity == null || type == null)
//			return;
//		Integer entity_id = getOrAddIdForString(entity);
//		this.addTypeForEntityId(entity_id, type);
//	}
	
	public void addTypeIdForEntity(String entity, int type_id) {
		if (entity == null)
			return;
		Integer entity_id = getOrAddIdForString(entity);
		addTypeIdForEntityId(entity_id, type_id);
	}
	
	public void addTypeIdForEntityId(int entity_id, int type_id) {
		HashSet<Integer> types = this.entity_to_types.get(entity_id);
		if (types == null) {
			types = new HashSet<Integer>();
			this.entity_to_types.put(entity_id, types);
		}
		types.add(type_id);
	}
	
//	private void addTypeForEntityId(Integer entity_id, String type) {
//		Integer type_id = getOrAddIdForString(type);
//		HashSet<Integer> types = this.entity_to_types.get(entity_id);
//		if (types == null) {
//			types = new HashSet<Integer>();
//			this.entity_to_types.put(entity_id, types);
//		}
//		types.add(type_id);
//	}
	
	public HashSet<Integer> getTypesForEntity(String entity) {
		if (entity == null)
			return null;
		
		Integer entity_id = string_to_id_map.get(entity);
		return getTypesForEntityId(entity_id);
	}
	
	public HashSet<Integer> getTypesForEntityId(Integer entity_id) {
		if (entity_id == null)
			return null;
		return entity_to_types.get(entity_id);
	}
	
	public void addPropertyForType(String type, String property) {
		if (property == null || type == null)
			return;
		Integer type_id = getOrAddIdForString(type);
		this.addPropertyForTypeId(type_id, property);
	}
	
	public void addPropertyForTypeId(Integer type_id, String property) {
		Integer property_id = getOrAddIdForString(property);
		HashSet<Integer> properties = this.type_to_properties.get(type_id);
		if (properties == null) {
			properties = new HashSet<Integer>();
			this.type_to_properties.put(type_id, properties);
		}
		properties.add(property_id);
	}
	
	public HashSet<Integer> getPropertiesForType(String type) {
		if (type == null)
			return null;
		
		Integer type_id = string_to_id_map.get(type);
		return getPropertiesForTypeId(type_id);
	}
	
	public HashSet<Integer> getPropertiesForTypeId(Integer type_id) {
		if (type_id == null)
			return null;
		return this.type_to_properties.get(type_id);
	}
	
	
	private void addFact(Integer subj_id, Integer pred_id, Integer obj_id) {
		HashMap<Integer, HashSet<Integer> > pred_objects = this.known_facts.get(subj_id);
		if (pred_objects == null) {
			pred_objects = new HashMap<Integer, HashSet<Integer> >();
			this.known_facts.put(subj_id, pred_objects);
		}
		HashSet<Integer> objects = pred_objects.get(pred_id);
		if (objects == null) {
			objects = new HashSet<Integer>();
			pred_objects.put(pred_id, objects);
		}
		objects.add(obj_id);
		
		PredicateInfo pred_info = getPredicateInfo(pred_id);
		addTypeIdForEntityId(subj_id, pred_info.getDomainId());
		addTypeIdForEntityId(obj_id, pred_info.getRangeId());
		
		HashMap<Integer, HashSet<Integer> > pred_subjects = this.reverse_facts.get(obj_id);
		if (pred_subjects == null) {
			pred_subjects = new HashMap<Integer, HashSet<Integer> >();
			this.reverse_facts.put(obj_id, pred_subjects);
		}
		HashSet<Integer> subjects = pred_subjects.get(pred_id);
		if (subjects == null) {
			subjects = new HashSet<Integer>();
			pred_subjects.put(pred_id, subjects);
		}
		subjects.add(subj_id);
	}
	
	public void addFact(String subj, String pred, String obj) {
		Integer subj_id = this.getIdForString(subj);
		if (subj_id == null) {
			if (!TextUtils.isMid(subj))
				subj_id = this.getOrAddIdForString(subj);
			else
				return;
		}
		Integer pred_id = this.getIdForString(pred);
		if (pred_id == null)
			return;
		Integer obj_id = this.getIdForString(obj);
		if (obj_id == null) {
			if (!TextUtils.isMid(obj)) {
				Double num = TextUtils.parseNumericString(obj);
				if (num == null) {
					Date date = Date.parseKBDate(obj);
					if (date != null) {
						obj = date.toString();
					}
				} else {
					obj = Double.toString(num);
				}
				obj_id = this.getOrAddIdForString(obj);
			} else
				return;
		}
		addFact(subj_id, pred_id, obj_id);
		PredicateInfo pred_info = getPredicateInfo(pred_id);
		Integer reverse_id = pred_info.getReverseId();
		if (reverse_id != null) {
			addFact(obj_id, reverse_id, subj_id);
		}
		IntPair ent_pair = new IntPair(subj_id, obj_id);
		HashSet<Integer> preds = ent_pair_to_preds.get(ent_pair);
		if (preds == null) {
			preds = new HashSet<Integer>();
			ent_pair_to_preds.put(ent_pair, preds);
		}
		preds.add(pred_id);
		
		if (reverse_id != null) {
			ent_pair = new IntPair(obj_id, subj_id);
			preds = ent_pair_to_preds.get(ent_pair);
			if (preds == null) {
				preds = new HashSet<Integer>();
				ent_pair_to_preds.put(ent_pair, preds);
			}
			preds.add(reverse_id);
		}
	}
	
	public boolean isKnownFact(String subj, String pred, String obj) {
		Integer subj_id = getIdForString(subj);
		if (subj_id == null) {
			return false;
		}
		Integer pred_id = getIdForString(pred);
		if (pred_id == null) {
			return false;
		}
		Integer obj_id = getIdForString(obj);
		if (obj_id == null) {
			return false;
		}
		HashSet<Integer> preds = ent_pair_to_preds.get(new IntPair(subj_id, obj_id));
		if (preds == null) {
			return false;
		}
		return preds.contains(pred_id);
	}
	
	public HashSet<Integer> getPredsForSubjObjPair(Integer subj_id, Integer obj_id) {
		return ent_pair_to_preds.get(new IntPair(subj_id, obj_id));
	}
	
	public HashSet<Integer> getPredsForSubjObjPair(IntPair ent_pair) {
		return ent_pair_to_preds.get(ent_pair);
	}
	
	public HashMap<Integer, HashSet<Integer> > getSubjFactCompletions(Integer subj_id) {
		return known_facts.get(subj_id);
	}
	
	public HashSet<Integer> getObjectsForSubjAndPredPair(Integer subj_id, Integer pred_id) {
		HashMap<Integer, HashSet<Integer> > completions = getSubjFactCompletions(subj_id);
		if (completions == null)
			return new HashSet<Integer>();
		HashSet<Integer> objects = completions.get(pred_id);
		if (objects == null)
			return new HashSet<Integer>();
		return objects;
	}
	
	public HashSet<Integer> getSubjectsForObjAndPredPair(Integer obj_id, Integer pred_id) {
		HashMap<Integer, HashSet<Integer> > completions = reverse_facts.get(obj_id);
		if (completions == null)
			return new HashSet<Integer>();
		HashSet<Integer> subjects = completions.get(pred_id);
		if (subjects == null)
			return new HashSet<Integer>();
		return subjects;
	}
	
	public int getNumPredicates() { return this.predicate_info.size(); }
	
	public void addPredicateInfo(PredicateInfo p_info) {
		this.predicate_info.put(p_info.getId(), p_info);
		HashSet<Integer> properties = type_to_properties.get(p_info.getDomainId());
		if (properties == null) {
			properties = new HashSet<Integer>();
			type_to_properties.put(p_info.getDomainId(), properties);
		}
		properties.add(p_info.getId());
	}
	
	public PredicateInfo getPredicateInfo(Integer pred_id) {
		return this.predicate_info.get(pred_id);
	}
	
	
	public Integer getOrMakeReversePredicateId(Integer pred_id) {
		PredicateInfo p_info = predicate_info.get(pred_id);
		if (p_info == null) {
			return null;
		}
		Integer reverse_id = p_info.getReverseId();
		if (reverse_id == null) {
			String new_reverse = "REV_" + id_to_string_map.get(pred_id);
			reverse_id = getOrAddIdForString(new_reverse);
			PredicateInfo reverse_info = new PredicateInfo(reverse_id, p_info.getRangeId(), p_info.getDomainId(), pred_id, false);
			addPredicateInfo(reverse_info);
			p_info.setReverseId(reverse_id);
			return reverse_id;
 		} else {
			return reverse_id;
		}
	}
	
	protected HashMap<Integer, Integer> findAllPredicatesForDomainsAndRanges(HashSet<Integer> domains,
																	HashSet<Integer> ranges) {
		HashSet<Integer> predicate_ids = new HashSet<Integer>();
		HashMap<Integer, Integer> predicate_ids_to_cnts = new HashMap<Integer, Integer>();
		Iterator<Integer> domain_it = domains.iterator();
		while (domain_it.hasNext()) {
			Integer domain_id = domain_it.next();
			HashSet<Integer> type_properties = type_to_properties.get(domain_id);
			if (type_properties == null) {
				continue;
			}
			Iterator<Integer> property_it = type_properties.iterator();
			while (property_it.hasNext()) {
				Integer predicate_id = property_it.next();
				PredicateInfo p_info = predicate_info.get(predicate_id);
				if (p_info == null) {
					continue;
				}
				if (ranges == null || ranges.contains(p_info.getRangeId())) {
					predicate_ids.add(predicate_id);
					Integer cnt = predicate_ids_to_cnts.get(predicate_id);
					if (cnt == null) {
						predicate_ids_to_cnts.put(predicate_id, 1);	
					} else {
						predicate_ids_to_cnts.put(predicate_id, cnt.intValue() + 1);
					}
				}
			}
 		}
		return predicate_ids_to_cnts;
	}
	
	protected HashSet<Integer> retrieveAllValidCvtObjects(int row) {
		HashSet<Integer> cvt_entities = new HashSet<Integer>();
		for (int col = 0; col < num_cols; col++) {
			
			Integer[] par_entities = cell_entities[col][row];
			if (par_entities != null) {
				for (Integer par_entity : par_entities) {
					HashMap<Integer, HashSet<Integer> > pred_objects = known_facts.get(par_entity);
					if (pred_objects == null)
						continue;
					for (Integer pred_id : pred_objects.keySet()) {
						if (predicates_expecting_cvts.contains(pred_id)) {
							HashSet<Integer> objects = pred_objects.get(pred_id);
							if (objects != null) {
								cvt_entities.addAll(objects);
							}
						}
					}
				}
			}
		}
		return cvt_entities;
	}
	
	protected SortedSet<Map.Entry<Pair<Integer, Integer>, Integer> > findPossibleTableTopics(Discrete[] column_variables) {
		HashMap<Pair<Integer, Integer>, Integer> topic_counts = new HashMap<Pair<Integer, Integer>, Integer>();
		
		for (int row = 0; row < column_variables.length; row++) {
			HashSet<Pair<Integer, Integer> > topics = new HashSet<Pair<Integer, Integer> >();
			Object[] entity_ids = column_variables[row].getDomain().getElements();
			for (Object entity_id : entity_ids) {
	 			HashMap<Integer, HashSet<Integer> > pred_objects = known_facts.get((Integer)entity_id);
				if (pred_objects != null) {
					for (Integer pred_id : pred_objects.keySet()) {
						PredicateInfo pred_info = getPredicateInfo(pred_id);
						if (pred_info == null) {
							continue;
						}
						if (pred_info.getReverseId() == null || getStringForId(pred_info.getReverseId()).startsWith("REV_")) {
							continue;
						}
						if (predicates_expecting_cvts.contains(pred_id)) {
							continue;
						}
						HashSet<Integer> object_ids = pred_objects.get(pred_id);
						for (Integer object_id : object_ids) {
							String object_str = getStringForId(object_id);
							Double object_double = TextUtils.parseNumericString(object_str);
							if (object_double != null) {
								continue;
							}
							Date object_date = Date.parseDateString(object_str);
							if (object_date != null) {
								continue;
							}
							if (getTypesForEntityId(object_id) == null) {
								continue;
							}
							if (!visible_entities.contains(object_id)) {
								Pair<Integer, Integer> po_pair = new Pair<Integer, Integer>(pred_id, object_id);
								topics.add(po_pair);
							}
						}
					}
				}
			}
			for (Pair<Integer, Integer> po_pair : topics) {
				Integer cnt = topic_counts.get(po_pair);
				if (cnt == null) {
					topic_counts.put(po_pair, 1);
				} else {
					topic_counts.put(po_pair, (cnt.intValue() + 1));
				}
			}
		}
		
		SortedSet<Map.Entry<Pair<Integer, Integer>, Integer> > sorted_topic_counts = new TreeSet<Map.Entry<Pair<Integer, Integer>, Integer> >(
				new Comparator<Map.Entry<Pair<Integer, Integer>, Integer> >() {
					public int compare(Map.Entry<Pair<Integer, Integer>, Integer> e1, Map.Entry<Pair<Integer, Integer>, Integer> e2) {
						return e2.getValue().compareTo(e1.getValue());
					}
				});
		for (Map.Entry<Pair<Integer, Integer>, Integer> entry : topic_counts.entrySet()) {
			if (entry.getValue() > 2) {
				sorted_topic_counts.add(entry);
			}
		}
		return sorted_topic_counts;
	}
	
	
	protected SortedSet<Map.Entry<Pair<Integer, Integer>, Integer> > findPossibleTableTopics(int col) {
		if (col >= num_cols) {
			return null;
		}
		Integer[][] root_column_entities = cell_entities[col];
		HashMap<Pair<Integer, Integer>, Integer> topic_counts = new HashMap<Pair<Integer, Integer>, Integer>();
		HashSet<Integer> seen_entities = new HashSet<Integer>();
		
		for (int row = 0; row < num_rows; row++) {
			HashSet<Pair<Integer, Integer> > topics = new HashSet<Pair<Integer, Integer> >();
			Integer[] cell_entities = root_column_entities[row];
			if (cell_entities != null) {
				for (int ent = 0; ent < cell_entities.length; ent++) {
					Integer entity_id = cell_entities[ent];
					if (seen_entities.contains(entity_id)) {
						continue;
					}
					seen_entities.add(entity_id);
					HashMap<Integer, HashSet<Integer> > pred_objects = known_facts.get(entity_id);
					if (pred_objects != null) {
						for (Integer pred_id : pred_objects.keySet()) {
							PredicateInfo pred_info = getPredicateInfo(pred_id);
							if (pred_info == null) {
								continue;
							}
							if (pred_info.getReverseId() == null || getStringForId(pred_info.getReverseId()).startsWith("REV_")) {
								continue;
							}
							if (predicates_expecting_cvts.contains(pred_id)) {
								continue;
							}
							HashSet<Integer> object_ids = pred_objects.get(pred_id);
							for (Integer object_id : object_ids) {
								String object_str = getStringForId(object_id);
								Double object_double = TextUtils.parseNumericString(object_str);
								if (object_double != null) {
									continue;
								}
								Date object_date = Date.parseDateString(object_str);
								if (object_date != null) {
									continue;
								}
								if (getTypesForEntityId(object_id) == null) {
									continue;
								}
								if (!visible_entities.contains(object_id)) {
									Pair<Integer, Integer> po_pair = new Pair<Integer, Integer>(pred_id, object_id);
									topics.add(po_pair);
								}
							}
						}
					}
				}
				for (Pair<Integer, Integer> po_pair : topics) {
					Integer cnt = topic_counts.get(po_pair);
					if (cnt == null) {
						topic_counts.put(po_pair, 1);
					} else {
						topic_counts.put(po_pair, (cnt.intValue() + 1));
					}
				}
			}
		}
		
		SortedSet<Map.Entry<Pair<Integer, Integer>, Integer> > sorted_topic_counts = new TreeSet<Map.Entry<Pair<Integer, Integer>, Integer> >(
				new Comparator<Map.Entry<Pair<Integer, Integer>, Integer> >() {
					public int compare(Map.Entry<Pair<Integer, Integer>, Integer> e1, Map.Entry<Pair<Integer, Integer>, Integer> e2) {
						return e2.getValue().compareTo(e1.getValue());
					}
				});
		for (Map.Entry<Pair<Integer, Integer>, Integer> entry : topic_counts.entrySet()) {
			if (entry.getValue() > 2) {
				sorted_topic_counts.add(entry);
			}
		}
		return sorted_topic_counts;
	}
	
	
}
